El conjunto de datos de Fraude en Cuentas Bancarias (BAF) ha sido publicado en NeurIPS 2022 y consta de un total de 6 conjuntos de datos tabulares sintéticos de fraudes en cuentas bancarias diferentes. BAF es un banco de pruebas realista, completo y sólido para evaluar métodos novedosos y existentes en aprendizaje automático y aprendizaje automático justo, y es el primero de su tipo.
Estos conjuntos de datos son:
Se desea predecir los fraudes en la apertura de cuentas bancarias en línea. Por tanto, todas las variables del dataset que se empleen para el modelo, deben poderse utilizar en el momento de su llamada. Para ello, se empleará un algoritmo de clasificación supervisado.
Los pasos a realizar son:
import pandas as pd
import numpy as np
import seaborn as sns
from matplotlib import pyplot as plt
import plotly.express as px
pd.set_option('display.max_columns', 500)
pd.set_option('display.max_rows', 5000)
def dame_variables_categoricas(dataset=None):
'''
----------------------------------------------------------------------------------------------------------
Función dame_variables_categoricas:
----------------------------------------------------------------------------------------------------------
-Descripción: Función que recibe un dataset y devuelve una lista con los nombres de las
variables categóricas
-Inputs:
-- dataset: Pandas dataframe que contiene los datos
-Return:
-- lista_variables_categoricas: lista con los nombres de las variables categóricas del
dataset de entrada con menos de 100 valores diferentes
-- 1: la ejecución es incorrecta
'''
if dataset is None:
print(u'\nFaltan argumentos por pasar a la función')
return 1
lista_variables_categoricas = []
other = []
for i in dataset.columns:
if (dataset[i].dtype!=float) & (dataset[i].dtype!=int):
unicos = int(len(np.unique(dataset[i].dropna(axis=0, how='all'))))
if unicos < 100:
lista_variables_categoricas.append(i)
else:
other.append(i)
return lista_variables_categoricas, other
path_folder = "./"
df_base = pd.read_csv(path_folder +"Base.csv", low_memory=False)
df_base.shape
(1000000, 32)
Entendemos que el modelo se va a ejecutar cuando los clientes se den de alta en la empresa. Con este modelo queremos detectar si un cliente si es fraudulento o no. El siguiente paso es analizar todas las variables del dataset e identificar las posibles variables futuras. Después de analizarlas podemos afirmar que las variables presentadas parecen ser características o atributos que describen diferentes aspectos de una solicitud o aplicación, algunas de las cuales pueden ser indicadores o características potenciales para predecir si una solicitud es fraudulenta o no. Esto quiere decir que no encontramos variables a futuro.
print(df_base.shape, df_base.drop_duplicates().shape)
(1000000, 32) (1000000, 32)
Esto significa que el dataset esta compuesto de 1 millón de registros y se compone de un total de 32 columnas. Al obtener dos veces el mismo resultado, podemos confirmar que no hay columnas duplicadas.
df_base.dtypes.to_dict()
{'fraud_bool': dtype('int64'),
'income': dtype('float64'),
'name_email_similarity': dtype('float64'),
'prev_address_months_count': dtype('int64'),
'current_address_months_count': dtype('int64'),
'customer_age': dtype('int64'),
'days_since_request': dtype('float64'),
'intended_balcon_amount': dtype('float64'),
'payment_type': dtype('O'),
'zip_count_4w': dtype('int64'),
'velocity_6h': dtype('float64'),
'velocity_24h': dtype('float64'),
'velocity_4w': dtype('float64'),
'bank_branch_count_8w': dtype('int64'),
'date_of_birth_distinct_emails_4w': dtype('int64'),
'employment_status': dtype('O'),
'credit_risk_score': dtype('int64'),
'email_is_free': dtype('int64'),
'housing_status': dtype('O'),
'phone_home_valid': dtype('int64'),
'phone_mobile_valid': dtype('int64'),
'bank_months_count': dtype('int64'),
'has_other_cards': dtype('int64'),
'proposed_credit_limit': dtype('float64'),
'foreign_request': dtype('int64'),
'source': dtype('O'),
'session_length_in_minutes': dtype('float64'),
'device_os': dtype('O'),
'keep_alive_session': dtype('int64'),
'device_distinct_emails_8w': dtype('int64'),
'device_fraud_count': dtype('int64'),
'month': dtype('int64')}
A través de esta función hemos querido obtener la tipología de las variables que componen el dataset.
porcentaje_fraude = df_base['fraud_bool'].value_counts(normalize=True) * 100
recuento_fraude = df_base['fraud_bool'].value_counts()
tabla_fraude = pd.DataFrame({'Recuento': recuento_fraude, 'Porcentaje (%)': porcentaje_fraude})
tabla_fraude.index.name = 'Valor de fraud_bool'
tabla_fraude = tabla_fraude.reset_index()
tabla_fraude
| Valor de fraud_bool | Recuento | Porcentaje (%) | |
|---|---|---|---|
| 0 | 0 | 988971 | 98.8971 |
| 1 | 1 | 11029 | 1.1029 |
Con esta tabla que hemos creado, hemos observado que casi el 99% del dataset estarían identificados como clientes no fraudulentos. En cambio, el 1% restante serían considerados clientes fraudulentos. Esta variables se encuentra muy descompensada, por lo que en un futuro, habría que hacer un oversampling o undersampling.
fig = px.histogram(tabla_fraude, x='Valor de fraud_bool', y=['Porcentaje (%)'])
fig.show()
Para que se pueda apreciar la descompensación de la variable 'fraud_bool', hemos querido hacer una representación gráfica de la misma.
null_columns = df_base.isnull().sum().sort_values(ascending=False)
null_rows = df_base.isnull().sum(axis=1).sort_values(ascending=False)
print(null_columns.shape, null_rows.shape)
(32,) (1000000,)
df_null_columnas = pd.DataFrame(null_columns, columns=['nulos_columnas'])
df_null_filas = pd.DataFrame(null_rows, columns=['nulos_filas'])
df_null_filas['target'] = df_base['fraud_bool'].copy()
df_null_columnas['porcentaje_columnas'] = df_null_columnas['nulos_columnas']/df_base.shape[0]
df_null_filas['porcentaje_filas']= df_null_filas['nulos_filas']/df_base.shape[1]
Este análisis explica cómo los valores nulos están previamente tratados, y queríamos confirmar la descricpión del dataset: no hay valores 0 ni nulos. Por otro lado, vamos a analizar tanto las filas como las columnas para comprobar que no hay nulos ni valores 0.
df_null_columnas
| nulos_columnas | porcentaje_columnas | |
|---|---|---|
| fraud_bool | 0 | 0.0 |
| income | 0 | 0.0 |
| device_fraud_count | 0 | 0.0 |
| device_distinct_emails_8w | 0 | 0.0 |
| keep_alive_session | 0 | 0.0 |
| device_os | 0 | 0.0 |
| session_length_in_minutes | 0 | 0.0 |
| source | 0 | 0.0 |
| foreign_request | 0 | 0.0 |
| proposed_credit_limit | 0 | 0.0 |
| has_other_cards | 0 | 0.0 |
| bank_months_count | 0 | 0.0 |
| phone_mobile_valid | 0 | 0.0 |
| phone_home_valid | 0 | 0.0 |
| housing_status | 0 | 0.0 |
| email_is_free | 0 | 0.0 |
| credit_risk_score | 0 | 0.0 |
| employment_status | 0 | 0.0 |
| date_of_birth_distinct_emails_4w | 0 | 0.0 |
| bank_branch_count_8w | 0 | 0.0 |
| velocity_4w | 0 | 0.0 |
| velocity_24h | 0 | 0.0 |
| velocity_6h | 0 | 0.0 |
| zip_count_4w | 0 | 0.0 |
| payment_type | 0 | 0.0 |
| intended_balcon_amount | 0 | 0.0 |
| days_since_request | 0 | 0.0 |
| customer_age | 0 | 0.0 |
| current_address_months_count | 0 | 0.0 |
| prev_address_months_count | 0 | 0.0 |
| name_email_similarity | 0 | 0.0 |
| month | 0 | 0.0 |
threshold=0.9
list_vars_not_null = list(df_null_columnas[df_null_columnas['porcentaje_columnas']<threshold].index)
df_base_filter_null = df_base.loc[:, list_vars_not_null]
df_base_filter_null.shape
(1000000, 32)
df_null_filas
| nulos_filas | target | porcentaje_filas | |
|---|---|---|---|
| 0 | 0 | 1 | 0.0 |
| 666657 | 0 | 0 | 0.0 |
| 666659 | 0 | 0 | 0.0 |
| 666660 | 0 | 0 | 0.0 |
| 666661 | 0 | 0 | 0.0 |
| ... | ... | ... | ... |
| 333337 | 0 | 0 | 0.0 |
| 333338 | 0 | 0 | 0.0 |
| 333339 | 0 | 0 | 0.0 |
| 333340 | 0 | 0 | 0.0 |
| 999999 | 0 | 0 | 0.0 |
1000000 rows × 3 columns
variables = ['prev_address_months_count', 'intended_balcon_amount', 'bank_months_count', 'session_length_in_minutes', 'device_distinct_emails_8w']
missing_count = []
missing_percentage = []
for variable in variables:
if variable == 'intended_balcon_amount':
missing_count.append(df_base[df_base[variable] < 0].shape[0])
else:
missing_count.append(df_base[df_base[variable] == -1].shape[0])
missing_percentage.append((missing_count[-1] / len(df_base)) * 100)
missing_data_df = pd.DataFrame({
'Variable': variables,
'Missing': missing_count,
'Porcentaje Missing': missing_percentage
})
missing_data_df
| Variable | Missing | Porcentaje Missing | |
|---|---|---|---|
| 0 | prev_address_months_count | 712920 | 71.2920 |
| 1 | intended_balcon_amount | 742523 | 74.2523 |
| 2 | bank_months_count | 253635 | 25.3635 |
| 3 | session_length_in_minutes | 2015 | 0.2015 |
| 4 | device_distinct_emails_8w | 359 | 0.0359 |
Podemos observar cómo en estas cinco variables existen valores nulos pero, como han sido tratados previamente no afectan al análisis de nuestro dataset. Las variables que más valores de este tipo incluyen son 'prev_address_months_count' y 'intended_balcon_amount'
list_cat_vars, other = dame_variables_categoricas(dataset=df_base_filter_null)
df_base_filter_null[list_cat_vars] = df_base_filter_null[list_cat_vars].astype("category")
df_base_filter_null[list_cat_vars].head()
| fraud_bool | device_fraud_count | device_distinct_emails_8w | keep_alive_session | device_os | source | foreign_request | has_other_cards | bank_months_count | phone_mobile_valid | phone_home_valid | housing_status | email_is_free | employment_status | date_of_birth_distinct_emails_4w | payment_type | customer_age | month | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 0 | 1 | 0 | windows | INTERNET | 0 | 0 | 24 | 0 | 1 | BA | 0 | CA | 6 | AA | 50 | 7 |
| 1 | 1 | 0 | 1 | 0 | windows | INTERNET | 0 | 0 | 15 | 0 | 0 | BA | 1 | CA | 3 | AB | 50 | 7 |
| 2 | 1 | 0 | 1 | 0 | other | INTERNET | 0 | 0 | -1 | 1 | 0 | BA | 1 | CB | 14 | AC | 40 | 7 |
| 3 | 1 | 0 | 1 | 0 | linux | INTERNET | 0 | 1 | 31 | 1 | 0 | BA | 1 | CA | 6 | AB | 50 | 7 |
| 4 | 1 | 0 | 1 | 1 | macintosh | INTERNET | 0 | 0 | 31 | 0 | 1 | BA | 1 | CA | 2 | AB | 50 | 7 |
En este apartado hemos seleccionado únicamente las variables de tipo categóricas que ya están tratadas.
len(other)
5
list_cat_vars
['fraud_bool', 'device_fraud_count', 'device_distinct_emails_8w', 'keep_alive_session', 'device_os', 'source', 'foreign_request', 'has_other_cards', 'bank_months_count', 'phone_mobile_valid', 'phone_home_valid', 'housing_status', 'email_is_free', 'employment_status', 'date_of_birth_distinct_emails_4w', 'payment_type', 'customer_age', 'month']
df_base_filter_null['source'].value_counts()
source INTERNET 992952 TELEAPP 7048 Name: count, dtype: int64
Esta variable, como podemos observar, hace referencia a una clasificación basada en que, cuando te das de alta, existen dos medios para hacerlo: internet o la app. Los resultados reflejan que una mayor cantidad de usuarios se decantan por hacerlo a través de internet.
df_base_filter_null['device_os'].value_counts()
device_os other 342728 linux 332712 windows 263506 macintosh 53826 x11 7228 Name: count, dtype: int64
Esta variable nos ha parecido interesante estudiar su categoría porque muestra el sistema operativo a través del cual han realizado la petición. Nos ha llamado la atención especialmente que haya datos que no han sido capaces de detectar su sistema en 'other', así como el gran uso de Linux.
df_base_filter_null[other].head(10)
| credit_risk_score | bank_branch_count_8w | zip_count_4w | current_address_months_count | prev_address_months_count | |
|---|---|---|---|---|---|
| 0 | 185 | 1 | 769 | 88 | -1 |
| 1 | 259 | 718 | 366 | 144 | -1 |
| 2 | 177 | 1 | 870 | 132 | -1 |
| 3 | 110 | 1921 | 810 | 22 | -1 |
| 4 | 295 | 1990 | 890 | 218 | -1 |
| 5 | 199 | 5 | 732 | 30 | -1 |
| 6 | 272 | 13 | 876 | 152 | -1 |
| 7 | 83 | 40 | 901 | 18 | -1 |
| 8 | 222 | 2134 | 933 | 64 | -1 |
| 9 | 118 | 8 | 1176 | 60 | -1 |
Estas variables, registradas como 'other' la funcion no ha sabido categorizarlas como numéricas o categóricas. Consideramos que las variables son numéricas.
No hemos necesitado el procesamiento de variables como los meses, puesto que ya estaban tratadas y categorizadas previamente. Es el caso de month, que podría ser una variable categorizable según este código pero ya se encontraba tratada.
df_base_filter_null.to_csv("./01_processed.csv")